/*****************************************************************
BioZen
Copyright (C) 2011 The National Center for Telehealth and
Technology
Eclipse Public License 1.0 (EPL-1.0)
This library is free software; you can redistribute it and/or
modify it under the terms of the Eclipse Public License as
published by the Free Software Foundation, version 1.0 of the
License.
The Eclipse Public License is a reciprocal license, under
Section 3. REQUIREMENTS iv) states that source code for the
Program is available from such Contributor, and informs licensees
how to obtain it in a reasonable manner on or through a medium
customarily used for software exchange.
Post your updates and modifications to our GitHub or email to
t2@tee2.org.
This library is distributed WITHOUT ANY WARRANTY; without
the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the Eclipse Public License 1.0 (EPL-1.0)
for more details.
You should have received a copy of the Eclipse Public License
along with this library; if not,
visit http://www.opensource.org/licenses/EPL-1.0
*****************************************************************/
package com.t2.compassionMeditation;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;
import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.t2health.lib1.BioParameter;
import org.t2health.lib1.BioSensor;
import spine.SPINEFactory;
import spine.SPINEFunctionConstants;
import spine.SPINEListener;
import spine.SPINEManager;
import spine.SPINESensorConstants;
import spine.datamodel.Address;
import spine.datamodel.Data;
import spine.datamodel.Feature;
import spine.datamodel.FeatureData;
import spine.datamodel.HeartBeatData;
import spine.datamodel.MindsetData;
import spine.datamodel.Node;
import spine.datamodel.ServiceMessage;
import spine.datamodel.ShimmerData;
import spine.datamodel.functions.ShimmerNonSpineSetupSensor;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import bz.org.t2health.lib.activity.BaseActivity;
import com.t2.Constants;
import com.t2.R;
import com.t2.SpineReceiver;
import com.t2.SpineReceiver.BioFeedbackStatus;
import com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener;
import com.t2.antlib.ANTPlusService;
import com.t2.antlib.AntPlusManager;
import com.t2.biofeedback.device.shimmer.ShimmerDevice;
import com.t2.compassionUtils.MathExtra;
import com.t2.compassionUtils.Util;
import com.t2.dataouthandler.DataOutHandler;
import com.t2.dataouthandler.DataOutHandlerException;
import com.t2.dataouthandler.DataOutHandlerTags;
import com.t2.dataouthandler.DataOutPacket;
import com.t2.t2sensorlib.BigBrotherService;
public class Graphs1Activity extends BaseActivity implements OnBioFeedbackMessageRecievedListener,
SPINEListener, AntPlusManager.Callbacks {
private static final String TAG = "BFDemo";
private static final String KEY_NAME = "results_visible_ids_16";
private static final int BLUETOOTH_SETTINGS_ID = 987;
private static final int HEARTRATE_SHIMMER = 1;
private static final int HEARTRATE_ZEPHYR = 2;
private static final int HEARTRATE_ANT = 3;
private String mAppId = "bioZenGraphs";
private boolean mLogCatEnabled = true;
private boolean mLoggingEnabled = true;
private int mPrevSigQuality = 0;
private boolean mInternalSensorMonitoring = false;
/**
* Intent to start Big Brother service
*/
private PendingIntent mBigBrotherService;
private int mPollingPeriod = 30; // seconds
private int mSecondsWithoutActivityThreshold = 5; // seconds
private double mAccelerationThreshold = 12.0; // m/s^2
/**
* Application version info determined by the package manager
*/
private String mApplicationVersion = "";
/**
* The Spine manager contains the bulk of the Spine server.
*/
private static SPINEManager mManager;
/**
* This is a broadcast receiver. Note that this is used ONLY for command/status messages from the AndroidBTService
* All data from the service goes through the mail SPINE mechanism (received(Data data)).
*/
private SpineReceiver mCommandReceiver;
/**
* Static instance of this activity
*/
private static Graphs1Activity mInstance;
/**
* Timer for updating the UI
*/
private static Timer mDataUpdateTimer;
/**
* Timer for Resp Rate Average
*/
private static Timer mRespRateAverageTimer;
protected SharedPreferences sharedPref;
private boolean mPaused = false;
private Boolean mBluetoothEnabled = false;
// UI Elements
private Button mAddMeasureButton;
private Button mPauseButton;
private TextView mTextInfoView;
private TextView mMeasuresDisplayText;
private MindsetData currentMindsetData;
private GraphicalView mDeviceChartView;
private int mConfiguredGSRRange = ShimmerDevice.GSR_RANGE_HW_RES_3M3;
/**
* List of all BioParameters used in this activity
*/
private ArrayList<GraphBioParameter> mBioParameters = new ArrayList<GraphBioParameter>();
/**
* List of all currently PAIRED BioSensors
*/
private ArrayList<BioSensor> mBioSensors = new ArrayList<BioSensor>();
/**
* Class to help in saving received data to file
*/
private DataOutHandler mDataOutHandler;
/**
* Class to help in processing biometeric data
*/
private BioDataProcessor mBioDataProcessor = new BioDataProcessor(this);
// Charting stuff
private final static int SPINE_CHART_SIZE = 20;
private int mSpineChartX = 0;
private Node mShimmerNode = null;
/**
* Node object for shimmer device as returned by spine
*/
public Node mSpineNode = null;
private int numTicsWithoutData = 0;
private static Object mKeysLock = new Object();
private static Object mRespRateAverageLock = new Object();
// We'll use these to get easy access to parameters in the mBioParameters array
private int eegPos;
private int gsrPos;
private int emgPos;
private int ecgPos;
private int heartRatePos;
private int respRatePos;
private int skinTempPos;
private int eHealthAirFlowPos;
private int eHealthTempPos;
private int eHealthSpO2Pos;
private int eHealthHeartRatePos;
private int eHealthGSRPos;
boolean mIsActive = false;
int mDisplaySampleRate;
// int mHeartRateSource = HEARTRATE_SHIMMER;
int mHeartRateSource = HEARTRATE_ZEPHYR;
long mLastRespRateTime;
int mRespRateTotal;
int mRespRateIndex;
private boolean mDatabaseEnabled;
private boolean mAntHrmEnabled;
/**
* Static names dealing with the external database
*/
public static final String dDatabaseName = "";
public static final String dDesignDocName = "bigbrother-local";
public static final String dDesignDocId = "_design/" + dDesignDocName;
public static final String byDateViewName = "byDate";
/** Class to manage all the ANT messaging and setup */
private AntPlusManager mAntManager;
private boolean mAntServiceBound;
/** Shared preferences data filename. */
public static final String PREFS_NAME = "ANTDemo1Prefs";
/** Pair to any device. */
static final short ANT_WILDCARD = 0;
/** The default proximity search bin. */
private static final byte ANT_DEFAULT_BIN = 7;
/** The default event buffering buffer threshold. */
private static final short ANT_DEFAULT_BUFFER_THRESHOLD = 0;
/**
* Right now we're using only one shimmer node for all shimmer devices
* (since we address they by BT address)
* @return singleton for the shimmer node
*/
private Node getShimmerNode() {
if (mShimmerNode == null) {
mShimmerNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_SHIMMER));
mManager.getActiveNodes().add(mShimmerNode);
}
return mShimmerNode;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i(TAG, this.getClass().getSimpleName() + ".onCreate()");
// We don't want the screen to timeout in this activity
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
this.requestWindowFeature(Window.FEATURE_NO_TITLE); // This needs to happen BEFORE setContentView
setContentView(R.layout.graphs_activity_layout);
mInstance = this;
sharedPref = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
setRequestedOrientation (ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
mLoggingEnabled = SharedPref.getBoolean(this, "enable_logging", true);
mDatabaseEnabled = SharedPref.getBoolean(this, "database_enabled", false);
mAntHrmEnabled = SharedPref.getBoolean(this, "enable_ant_hrm", false);
mInternalSensorMonitoring = SharedPref.getBoolean(this, "inernal_sensor_monitoring_enabled", false);
if (mAntHrmEnabled) {
mHeartRateSource = HEARTRATE_ANT;
}
else {
mHeartRateSource = HEARTRATE_ZEPHYR;
}
// The session start time will be used as session id
// Note this also sets session start time
// **** This session ID will be prepended to all JSON data stored
// in the external database until it's changed (by the start
// of a new session.
Calendar cal = Calendar.getInstance();
SharedPref.setBioSessionId(sharedPref, cal.getTimeInMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US);
String sessionDate = sdf.format(new Date());
String userId = SharedPref.getString(this, "SelectedUser", "");
long sessionId = SharedPref.getLong(this, "bio_session_start_time", 0);
mDataOutHandler = new DataOutHandler(this, userId,sessionDate, mAppId, DataOutHandler.DATA_TYPE_EXTERNAL_SENSOR, sessionId );
if (mDatabaseEnabled) {
TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
String myNumber = telephonyManager.getLine1Number();
String remoteDatabaseUri = SharedPref.getString(this, "database_sync_name", getString(R.string.database_uri));
// remoteDatabaseUri += myNumber;
Log.d(TAG, "Initializing database at " + remoteDatabaseUri); // TODO: remove
try {
mDataOutHandler.initializeDatabase(dDatabaseName, dDesignDocName, dDesignDocId, byDateViewName, remoteDatabaseUri);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
mDataOutHandler.setRequiresAuthentication(false);
}
mBioDataProcessor.initialize(mDataOutHandler);
if (mLoggingEnabled) {
mDataOutHandler.enableLogging(this);
}
if (mLogCatEnabled) {
mDataOutHandler.enableLogCat();
}
// Log the version
try {
PackageManager packageManager = getPackageManager();
PackageInfo info = packageManager.getPackageInfo(getPackageName(), 0);
mApplicationVersion = info.versionName;
String versionString = mAppId + " application version: " + mApplicationVersion;
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.version, versionString);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
catch (NameNotFoundException e) {
Log.e(TAG, e.toString());
}
// Set up UI elements
Resources resources = this.getResources();
AssetManager assetManager = resources.getAssets();
mPauseButton = (Button) findViewById(R.id.buttonPause);
mAddMeasureButton = (Button) findViewById(R.id.buttonAddMeasure);
mTextInfoView = (TextView) findViewById(R.id.textViewInfo);
mMeasuresDisplayText = (TextView) findViewById(R.id.measuresDisplayText);
// Don't actually show skin conductance meter unless we get samples
ImageView image = (ImageView) findViewById(R.id.imageView1);
image.setImageResource(R.drawable.signal_bars0);
// Check to see of there a device configured for EEG, if so then show the skin conductance meter
String tmp = SharedPref.getString(this, "EEG" ,null);
if (tmp != null) {
image.setVisibility(View.VISIBLE);
}
else {
image.setVisibility(View.INVISIBLE);
}
// Initialize SPINE by passing the fileName with the configuration properties
try {
mManager = SPINEFactory.createSPINEManager("SPINETestApp.properties", resources);
} catch (InstantiationException e) {
Log.e(TAG, "Exception creating SPINE manager: " + e.toString());
e.printStackTrace();
}
try {
currentMindsetData = new MindsetData(this);
} catch (Exception e1) {
Log.e(TAG, "Exception creating MindsetData: " + e1.toString());
e1.printStackTrace();
}
// Establish nodes for BSPAN
// Create a broadcast receiver. Note that this is used ONLY for command messages from the service
// All data from the service goes through the mail SPINE mechanism (received(Data data)).
// See public void received(Data data)
this.mCommandReceiver = new SpineReceiver(this);
int itemId = 0;
eegPos = itemId; // eeg always comes first
mBioParameters.clear();
// First create GraphBioParameters for each of the EEG static params (ie mindset)
for (itemId = 0; itemId < MindsetData.NUM_BANDS + 2; itemId++) { // 2 extra, for attention and meditation
GraphBioParameter param = new GraphBioParameter(itemId, MindsetData.spectralNames[itemId], "", true);
param.isShimmer = false;
mBioParameters.add(param);
}
// Now create all of the potential dynamic GBraphBioParameters (GSR, EMG, ECG, EEG, HR, Skin Temp, Resp Rate
// String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names);
String[] paramNamesStringArray = getResources().getStringArray(R.array.parameter_names_less_eeg);
for (String paramName: paramNamesStringArray) {
if (paramName.equalsIgnoreCase("not assigned"))
continue;
GraphBioParameter param = new GraphBioParameter(itemId, paramName, "", true);
if (paramName.equalsIgnoreCase("gsr")) {
gsrPos = itemId;
param.isShimmer = true;
param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_GSR_SENSOR;
param.shimmerNode = getShimmerNode();
}
if (paramName.equalsIgnoreCase("emg")) {
emgPos = itemId;
param.isShimmer = true;
param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_EMG_SENSOR;
param.shimmerNode = getShimmerNode();
}
if (paramName.equalsIgnoreCase("ecg")) {
ecgPos = itemId;
param.isShimmer = true;
param.shimmerSensorConstant = SPINESensorConstants.SHIMMER_ECG_SENSOR;
param.shimmerNode = getShimmerNode();
}
if (paramName.equalsIgnoreCase("heart rate")) {
heartRatePos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("resp rate")) {
respRatePos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("skin temp")) {
skinTempPos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("EHealth Airflow")) {
eHealthAirFlowPos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("EHealth Temp")) {
eHealthTempPos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("EHealth SpO2")) {
eHealthSpO2Pos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("EHealth Heartrate")) {
eHealthHeartRatePos = itemId;
param.isShimmer = false;
}
if (paramName.equalsIgnoreCase("EHealth GSR")) {
eHealthGSRPos = itemId;
param.isShimmer = false;
}
itemId++;
mBioParameters.add(param);
}
// Since These are static nodes (Non-spine) we have to manually put them in the active node list
Node mindsetNode = null;
mindsetNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_MINDSET)); // Note that the sensor id 0xfff1 (-15) is a reserved id for this particular sensor
mManager.getActiveNodes().add(mindsetNode);
Node zepherNode = null;
zepherNode = new Node(new Address("" + Constants.RESERVED_ADDRESS_ZEPHYR));
mManager.getActiveNodes().add(zepherNode);
// The arduino node is programmed to look like a static Spine node
// Note that currently we don't have to turn it on or off - it's always streaming
// Since Spine (in this case) is a static node we have to manually put it in the active node list
// Since the
final int RESERVED_ADDRESS_ARDUINO_SPINE = 1; // 0x0001
mSpineNode = new Node(new Address("" + RESERVED_ADDRESS_ARDUINO_SPINE));
mManager.getActiveNodes().add(mSpineNode);
final String sessionName;
// Check to see if we were requested to play back a previous session
try {
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
sessionName = bundle.getString(BioZenConstants.EXTRA_SESSION_NAME);
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Replay Session " + sessionName + "?");
alert.setMessage("Make sure to turn off all Bluetooth Sensors!");
alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
try {
mDataOutHandler.logNote("Replaying data from session " + sessionName);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
replaySessionData(sessionName);
AlertDialog.Builder alert1 = new AlertDialog.Builder(mInstance);
alert1.setTitle("INFO");
alert1.setMessage("Replay of session complete!");
alert1.show();
}
});
alert.show();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
if (mInternalSensorMonitoring) {
// IntentSender Launches our service scheduled with with the alarm manager
mBigBrotherService = PendingIntent.getService(Graphs1Activity.this,
0, new Intent(Graphs1Activity.this, BigBrotherService.class), 0);
long firstTime = SystemClock.elapsedRealtime();
// Schedule the alarm!
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
firstTime, mPollingPeriod * 1000, mBigBrotherService);
// Tell the user about what we did.
Toast.makeText(Graphs1Activity.this, R.string.service_scheduled,
Toast.LENGTH_LONG).show();
}
//testFIRFilter();
// testHR();
} // End onCreate(
boolean replaySessionData(String sessionName)
{
BufferedReader logReader = null;
// Open a file for saving data
try {
File root = Environment.getExternalStorageDirectory();
if (root.canWrite()){
File gpxfile = new File(root, sessionName);
FileReader gpxreader = new FileReader(gpxfile); // open for append
logReader = new BufferedReader(gpxreader);
String lineToParse;
while ((lineToParse = logReader.readLine()) != null) {
Log.i("SensorData",lineToParse);
if (lineToParse.contains("ECG,")) {
try {
String[] tokens = lineToParse.split(",");
if (tokens.length == 7) {
ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
shimmerData.ecgLaLL = 2000 + (int) Float.parseFloat(tokens[5].trim());
shimmerData.ecgRaLL = 2000;
shimmerData.timestamp = Integer.parseInt(tokens[1].trim());
this.received(shimmerData);
}
if (tokens.length == 6) {
ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[4].trim());
shimmerData.ecgRaLL = 2000;
shimmerData.timestamp = Integer.parseInt(tokens[2].trim());
this.received(shimmerData);
}
if (tokens.length == 5) {
ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
shimmerData.ecgLaLL = 2000 + Integer.parseInt(tokens[3].trim());
shimmerData.ecgRaLL = 2000;
shimmerData.timestamp = Integer.parseInt(tokens[2].trim());
this.received(shimmerData);
}
} catch (Exception e) {
}
}
}
}
else {
Log.e(TAG, "Could not open file " );
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("ERROR");
alert.setMessage("Cannot open to file");
alert.show();
}
} catch (IOException e) {
Log.e(TAG, "Could not open file " + e.getMessage());
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("ERROR");
alert.setMessage("Cannot write to file");
alert.show();
}
try {
logReader.close();
} catch (IOException e) {
Log.e(TAG, "Could not close file " + e.getMessage());
}
return true;
}
private void testHR() {
byte[] input = {-22,-29,-33,-93,-48,27,-3,24,-20,-44,22,-40,-70,-82,33,-64,-23,4,-20,28,-78,3,7,23,92,43,14,19,-27,111,93,-38,93,-31,-29,-40,2,59,70,-76,-14,15,-44,-66,-17,-16,-19,-18,-10,-16,53,98,-23,-23,-15,-14,-8,-1,3,-20,-10,-26,-5,-23,-9,-6,18,18,17,16,33,56,46,10,7,9,20,0,12,10,8,8,-5,14,-5,6,-12,1,-9,22,-12,-7,-29,-18,59,47,-25,-31,-33,-39,-38,-27,-15,-8,-45,-42,-26,-15,-32,-13,8,4,21,31,17,22,8,17,5,16,2,0,3,34,1,-1,7,9,6,5,11,-9,20,7,-14,-17,29,99,15,-23,-32,-11,-27,-30,-24,-35,-18,-40,-22,-30,-34,-21,1,27,13,32,29,14,29,29,14,7,14,11,17,-5,9,-4,4,14,-1,8,11,6,12,8,-1,-40,50,98,-29,-19,-33,-22,-20,-30,-36,-5,-28,-28,-49,-45,-38,5,21,12,27,13,14,14,8,22,7,28,6,18,8,8,12,1,-7,-13,-22,19,-11,19,-7,4,-2,43,102,-17,-26,-33,-44,-15,-25,-36,-29,-19,-35,-24,-33,-10,-8,18,44,17,21,12,13,38,1,14,10,6,13,2,-2,12};
long timeStamp = 0;
for (int i = 0; i < input.length; i++) {
ShimmerData shimmerData = new ShimmerData(SPINEFunctionConstants.SHIMMER, SPINESensorConstants.SHIMMER_ECG_SENSOR, (byte) 0);
shimmerData.setFunctionCode(SPINEFunctionConstants.SHIMMER);
shimmerData.sensorCode = SPINESensorConstants.SHIMMER_ECG_SENSOR;
shimmerData.ecgLaLL = 2000 + input[i];
shimmerData.ecgRaLL = 2000;
shimmerData.timestamp = (int) timeStamp;
timeStamp += 512;
this.received(shimmerData);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mInternalSensorMonitoring) {
// And cancel the alarm.
AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
am.cancel(mBigBrotherService);
Intent intent = new Intent();
intent.setAction(BigBrotherConstants.ACTION_COMMAND_BROADCAST);
intent.putExtra("message", BigBrotherConstants.SERVICE_OFF);
sendBroadcast(intent);
// Tell the user about what we did.
Toast.makeText(Graphs1Activity.this, R.string.service_unscheduled,
Toast.LENGTH_LONG).show();
}
mDataOutHandler.close();
if (mDataUpdateTimer != null) {
mDataUpdateTimer.cancel();
mDataUpdateTimer.purge();
}
if (mRespRateAverageTimer != null) {
mRespRateAverageTimer.cancel();
mRespRateAverageTimer.purge();
}
Log.i(TAG, this.getClass().getSimpleName() + ".onDestroy()");
// Send stop command to every shimmer device
// You might think that it would be better to iterate through the mBioSensors table
// instead of the mBioParameters table but it's actually easier this way
for (GraphBioParameter param : mBioParameters) {
if (param.isShimmer && param.shimmerNode != null) {
ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor();
setup.setSensor(param.shimmerSensorConstant);
String deviceAddress = SharedPref.getDeviceForParam(this, param.title1);
if (deviceAddress != null) {
setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress));
setup.setCommand(ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_STOPPED);
Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_STOPPED",
param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant));
mManager.setup(param.shimmerNode, setup);
}
}
}
this.unregisterReceiver(this.mCommandReceiver);
}
@Override
protected void onStart() {
super.onStart();
mIsActive = true;
// we need to register a SPINEListener implementation to the SPINE manager instance
// to receive sensor node data from the Spine server
// (I register myself since I'm a SPINEListener implementation!)
mManager.addListener(this);
Log.i(TAG, this.getClass().getSimpleName() + ".onStart()");
// Set up filter intents so we can receive broadcasts
IntentFilter filter = new IntentFilter();
filter.addAction("com.t2.biofeedback.service.status.BROADCAST");
this.registerReceiver(this.mCommandReceiver,filter);
int displaySampleTime = 1000;
String s = SharedPref.getString(this, "display_sample_rate" ,"1");
mDisplaySampleRate = Integer.parseInt(s);
switch (mDisplaySampleRate) {
default:
case 1:
displaySampleTime = 1000;
Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
break;
case 10:
displaySampleTime = 100;
Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
break;
case 100:
displaySampleTime = 10;
Log.d(TAG, "Setting display sample rate to " + mDisplaySampleRate + " Hz");
break;
case 9999:
displaySampleTime = 9999;
Log.d(TAG, "Setting display sample rate to match sensor sample rate");
break;
}
if (mDisplaySampleRate != 9999) {
// Set up a timer to do graphical updates
mDataUpdateTimer = new Timer();
mDataUpdateTimer.schedule(new TimerTask() {
@Override
public void run() {
TimerMethod();
}
}, 0, displaySampleTime);
}
// Set up a timer for GSR average reporting (10 seconds
mRespRateAverageTimer = new Timer();
mRespRateAverageTimer.schedule(new TimerTask() {
@Override
public void run() {
respRateAverageMethod();
}
}, 0, 10000);
if (mAntHrmEnabled) {
mAntServiceBound = bindService(new Intent(this, ANTPlusService.class), mConnection, BIND_AUTO_CREATE);
}
}
@Override
protected void onStop() {
super.onStop();
if (mDataUpdateTimer != null)
mDataUpdateTimer.cancel();
if(mAntManager != null)
{
saveAntState();
mAntManager.setCallbacks(null);
if (mAntManager.isChannelOpen(AntPlusManager.HRM_CHANNEL))
{
Log.d(TAG, "onClick (HRM): Close channel");
mAntManager.closeChannel(AntPlusManager.HRM_CHANNEL);
}
}
if(mAntServiceBound)
{
unbindService(mConnection);
}
Log.i(TAG, this.getClass().getSimpleName() + ".onStop()");
}
@Override
protected void onPause() {
super.onPause();
mIsActive = false;
// *******************
// Make sure to to this or else you will get more and more notifications from Spine as you
// go into and out of activities!
// Also make sure to do this in on pause (as opposed to onStop or ondestroy.
// This will prevent you from receiving messages possibly requested by another activity
mManager.removeListener(this);
Log.i(TAG, this.getClass().getSimpleName() + ".onPause()");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, this.getClass().getSimpleName() + ".onResume()");
mLastRespRateTime = System.currentTimeMillis();
int mRespRateTotal = 0;
int mRespRateIndex = 0;
// Set up Device data chart
generateChart();
mManager.discoveryWsn(); // discoveryCompleted() is called after this is done
}
@Override
public void newNodeDiscovered(Node newNode) {
Log.d(TAG, this.getClass().getSimpleName() + ".newNodeDiscovered()" + newNode.toString());
}
@Override
public void received(ServiceMessage msg) {
}
/**
* This is where we receive sensor data that comes through the actual
* Spine channel.
* @param data Generic Spine data packet. Should be cast to specifid data type indicated by data.getFunctionCode()
*
* @see spine.SPINEListener#received(spine.datamodel.Data)
*/
@Override
public void received(Data data) {
//Log.d(TAG, this.getClass().getSimpleName() + ".received()");
if (data != null) {
switch (data.getFunctionCode()) {
// E-Health board
case SPINEFunctionConstants.FEATURE: {
FeatureData featureData = (FeatureData) data;
Feature[] feats = featureData.getFeatures();
if (feats.length < 2) {
break;
}
Feature firsFeat = feats[0];
Feature Feat2 = feats[1];
int airFlow = firsFeat.getCh1Value();
int scaledTemp = firsFeat.getCh2Value();
float temp = (float)scaledTemp/(65535F/9F) + 29F;
int BPM = firsFeat.getCh3Value();
int SPO2 = firsFeat.getCh4Value();
int scaledConductance = Feat2.getCh1Value();
float conductance = (float) scaledConductance / (65535F/4F);
Log.d(TAG, "E-health Values = " + airFlow + ", " + temp + ", " + BPM + ", " + SPO2 + ", " + conductance + ", " );
synchronized(mKeysLock) {
mBioParameters.get(eHealthAirFlowPos).rawValue = airFlow;
mBioParameters.get(eHealthAirFlowPos).scaledValue = (int) map(airFlow,0,360,0,100);
mBioParameters.get(eHealthTempPos).rawValue = (int) temp;
mBioParameters.get(eHealthTempPos).scaledValue = (int) map(temp,29,40,0,100);
mBioParameters.get(eHealthHeartRatePos).rawValue = BPM;
mBioParameters.get(eHealthHeartRatePos).scaledValue = (int) map(BPM,30,220,0,100);
mBioParameters.get(eHealthSpO2Pos).rawValue = SPO2;
mBioParameters.get(eHealthSpO2Pos).scaledValue = SPO2;
mBioParameters.get(eHealthGSRPos).rawValue = (int) map(scaledConductance,0,65535,0,100);
mBioParameters.get(eHealthGSRPos).scaledValue = (int) map(scaledConductance,0,65535,0,100);;
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.RAW_HEARTRATE, BPM);
packet.add(DataOutHandlerTags.RAW_GSR, conductance);
packet.add(DataOutHandlerTags.RAW_SKINTEMP, temp);
packet.add(DataOutHandlerTags.SPO2, SPO2);
packet.add(DataOutHandlerTags.AIRFLOW, airFlow);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
break;
}
case SPINEFunctionConstants.HEARTBEAT: {
synchronized(mKeysLock) {
HeartBeatData thisData = (HeartBeatData) data;
int scaled = (thisData.getBPM() )/2 ;
if (mHeartRateSource == HEARTRATE_ANT) {
mBioParameters.get(heartRatePos).rawValue = thisData.getBPM();
mBioParameters.get(heartRatePos).scaledValue = scaled;
}
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.RAW_HEARTRATE, thisData.getBPM());
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999 && mIsActive) {
this.runOnUiThread(Timer_Tick);
}
}
break;
}
case SPINEFunctionConstants.SHIMMER: {
Node node = data.getNode();
numTicsWithoutData = 0;
Node source = data.getNode();
ShimmerData shimmerData = (ShimmerData) data;
synchronized(mKeysLock) {
switch (shimmerData.sensorCode) {
case SPINESensorConstants.SHIMMER_GSR_SENSOR:
mBioDataProcessor.processShimmerGSRData(shimmerData, mConfiguredGSRRange);
mBioParameters.get(gsrPos).rawValue = (int) (mBioDataProcessor.mGsrConductance * 1000); // scale by 1000 to fit a float into an int
double scaled = mBioDataProcessor.mGsrConductance * 10;
mBioParameters.get(gsrPos).scaledValue = (int) scaled;
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999 && mIsActive) {
this.runOnUiThread(Timer_Tick);
}
break;
case SPINESensorConstants.SHIMMER_EMG_SENSOR:
mBioDataProcessor.processShimmerEMGData(shimmerData);
scaled = MathExtra.scaleData((float)shimmerData.emg, 4000F, 0F, 100);
mBioParameters.get(emgPos).rawValue = (int) scaled;
mBioParameters.get(emgPos).scaledValue = (int) scaled;
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999) {
this.runOnUiThread(Timer_Tick);
}
break;
case SPINESensorConstants.SHIMMER_ECG_SENSOR:
// If we're receiving packets from shimmer egg then swith the heartrate to shimmer
// Otherwise we'll leave it at the default which is zephyr
mHeartRateSource = HEARTRATE_SHIMMER;
mBioDataProcessor.processShimmerECGData(shimmerData);
scaled = (mBioDataProcessor.mRawEcg + 50 )/2 ;
mBioParameters.get(ecgPos).rawValue = (int) scaled;
mBioParameters.get(ecgPos).scaledValue = (int) scaled;
if (mHeartRateSource == HEARTRATE_SHIMMER) {
mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mShimmerHeartRate;
mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mShimmerHeartRate;
}
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999) {
this.runOnUiThread(Timer_Tick);
}
break;
}
}
break;
}
case SPINEFunctionConstants.ZEPHYR: {
numTicsWithoutData = 0;
mBioDataProcessor.processZephyrData(data);
synchronized(mKeysLock) {
if (mHeartRateSource == HEARTRATE_ZEPHYR) {
mBioParameters.get(heartRatePos).scaledValue = mBioDataProcessor.mZephyrHeartRate/3;
mBioParameters.get(heartRatePos).rawValue = mBioDataProcessor.mZephyrHeartRate;
}
mBioParameters.get(respRatePos).scaledValue = (int) mBioDataProcessor.mRespRate * 5;
mBioParameters.get(respRatePos).rawValue = (int) mBioDataProcessor.mRespRate;
mBioParameters.get(skinTempPos).scaledValue = (int) mBioDataProcessor.mSkinTempF;
mBioParameters.get(skinTempPos).rawValue = (int) mBioDataProcessor.mSkinTempF;
}
synchronized(mRespRateAverageLock) {
mRespRateTotal += mBioDataProcessor.mRespRate;
mRespRateIndex++;
}
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999) {
this.runOnUiThread(Timer_Tick);
}
break;
} // End case SPINEFunctionConstants.ZEPHYR:
case SPINEFunctionConstants.MINDSET: {
Node source = data.getNode();
MindsetData mindsetData = (MindsetData) data;
mBioDataProcessor.processMindsetData(data, currentMindsetData);
if (mindsetData.exeCode == Constants.EXECODE_SPECTRAL || mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM) {
numTicsWithoutData = 0;
synchronized(mKeysLock) {
for (int i = 0; i < MindsetData.NUM_BANDS + 2; i++) { // 2 extra, for attention and meditation
mBioParameters.get(i).scaledValue = currentMindsetData.getFeatureValue(i);
mBioParameters.get(i).rawValue = currentMindsetData.getFeatureValue(i);
}
}
// See if we are configured to update display every time we get sensor data
if (mDisplaySampleRate == 9999) {
this.runOnUiThread(Timer_Tick);
}
}
if (mindsetData.exeCode == Constants.EXECODE_POOR_SIG_QUALITY) {
// Now show signal strength as bars
int sigQuality = mindsetData.poorSignalStrength & 0xff;
ImageView image = (ImageView) findViewById(R.id.imageView1);
if (sigQuality == 200)
image.setImageResource(R.drawable.signal_bars0);
else if (sigQuality > 150)
image.setImageResource(R.drawable.signal_bars1);
else if (sigQuality > 100)
image.setImageResource(R.drawable.signal_bars2);
else if (sigQuality > 50)
image.setImageResource(R.drawable.signal_bars3);
else if (sigQuality > 25)
image.setImageResource(R.drawable.signal_bars4);
else
image.setImageResource(R.drawable.signal_bars5);
if (sigQuality == 200 && mPrevSigQuality != 200) {
Toast.makeText (getApplicationContext(), "Headset not makeing good skin contact. Please Adjust", Toast.LENGTH_LONG).show ();
}
mPrevSigQuality = sigQuality;
}
break;
} // End case SPINEFunctionConstants.MINDSET:
} // End switch (data.getFunctionCode())
} // End if (data != null)
}
// Note that this is really inaptly named. This simply gets called a certain time period after
// discovery is initiated (2 sec?)
@Override
public void discoveryCompleted(Vector activeNodes) {
Log.d(TAG, this.getClass().getSimpleName() + ".discoveryCompleted()");
// Tell the bluetooth service to send us a list of bluetooth devices and system status
// Response comes in public void onStatusReceived(BioFeedbackStatus bfs) STATUS_PAIRED_DEVICES
mManager.pollBluetoothDevices();
}
/**
* This callback is called whenever the AndroidBTService sends us an indication that
* it is actively trying to establish a BT connection to one of the nodes.
*
* @see com.t2.SpineReceiver.OnBioFeedbackMessageRecievedListener#onStatusReceived(com.t2.SpineReceiver.BioFeedbackStatus)
*/
@Override
public void onStatusReceived(BioFeedbackStatus bfs) {
String name = bfs.name;
if (name == null ) name = "sensor node";
if(bfs.messageId.equals("CONN_CONNECTING")) {
Log.d(TAG, "Received command : " + bfs.messageId + " to " + name );
Toast.makeText (getApplicationContext(), "Connecting to " + name, Toast.LENGTH_SHORT).show ();
}
else if(bfs.messageId.equals("CONN_ANY_CONNECTED")) {
Log.d(TAG, "Received command : " + bfs.messageId + " to " + name );
// Something has connected - discover what it was
mManager.discoveryWsn();
Toast.makeText (getApplicationContext(), name + " Connected", Toast.LENGTH_SHORT).show ();
}
else if(bfs.messageId.equals("CONN_CONNECTION_LOST")) {
Log.d(TAG, "Received command : " + bfs.messageId + " to " + name );
Toast.makeText (getApplicationContext(), name + " Connection lost ****", Toast.LENGTH_SHORT).show ();
}
else if(bfs.messageId.equals("STATUS_PAIRED_DEVICES")) {
Log.d(TAG, "Received command : " + bfs.messageId + " to " + name );
Log.d(TAG, bfs.address );
// We don't want to take any action unless we're ready to go
if (!mIsActive)
return;
populateBioSensors(bfs.address);
validateBioSensors();
// Send startup command to every shimmer device
for (GraphBioParameter param : mBioParameters) {
if (param.isShimmer && param.shimmerNode != null) {
ShimmerNonSpineSetupSensor setup = new ShimmerNonSpineSetupSensor();
setup.setSensor(param.shimmerSensorConstant);
String deviceAddress = SharedPref.getDeviceForParam(this, param.title1);
if (deviceAddress != null) {
setup.setBtAddress(Util.AsciiBTAddressToBytes(deviceAddress));
byte startShimmercommand;
String s = SharedPref.getString(this, "sensor_sample_rate" ,"4");
int sensorSampleRate = Integer.parseInt(s);
Log.d(TAG, "Initializing sensor sample rate to " + sensorSampleRate);
switch (sensorSampleRate) {
default:
case 4:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_4HZ_AUTORANGE;
break;
case 10:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_10HZ_AUTORANGE;
break;
case 32:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_32HZ_AUTORANGE;
break;
case 50:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_50HZ_AUTORANGE;
break;
case 64:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_64HZ_AUTORANGE;
break;
case 100:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_100HZ_AUTORANGE;
break;
case 125:
startShimmercommand = ShimmerNonSpineSetupSensor.SHIMMER_COMMAND_RUNNING_125HZ_AUTORANGE;
break;
}
setup.setCommand(startShimmercommand);
mConfiguredGSRRange = Util.getGsrRangeFromShimmerCommand(startShimmercommand);
Log.d(TAG, String.format("Setting up Shimmer sensor: %s (%s) (%d) SHIMMER_COMMAND_RUNNING",
param.shimmerNode.getPhysicalID(), deviceAddress, param.shimmerSensorConstant));
mManager.setup(param.shimmerNode, setup);
}
else {
}
}
}
}
}
/**
* This method is called directly by the timer and runs in the same thread as the timer
* From here We call the method that will work with the UI through the runOnUiThread method.
*/
private void TimerMethod() {
this.runOnUiThread(Timer_Tick);
}
/**
* This method runs in the same thread as the UI.
*/
private Runnable Timer_Tick = new Runnable() {
public void run() {
numTicsWithoutData++;
if (mPaused == true || currentMindsetData == null) {
// if (mPaused == true || currentMindsetData == null || numTicsWithoutData > 2) {
return;
}
String bandValuesString = "";
// Output a point for each visible key item
int keyCount = mBioParameters.size();
for(int i = 0; i < mBioParameters.size(); ++i) {
GraphBioParameter item = mBioParameters.get(i);
int rawValue = item.rawValue;
int scaledValue = item.scaledValue;
if(!item.visible) {
continue;
}
// Special case for GSR since it's actually a float scaled to fit into an int
if (gsrPos == i) {
float conductance = (float) rawValue / 1000F;
bandValuesString += item.title1 + ":" + conductance + ", ";
}
else {
bandValuesString += item.title1 + ":" + rawValue + ", ";
}
item.series.add(mSpineChartX, scaledValue);
if (item.series.getItemCount() > SPINE_CHART_SIZE) {
item.series.remove(0);
}
}
mSpineChartX++;
if (mDeviceChartView != null) {
mDeviceChartView.repaint();
}
mTextInfoView.setText(bandValuesString);
}
};
private void respRateAverageMethod() {
this.runOnUiThread(respRate_Average_Tick);
}
/**
* This method runs in the same thread as the UI.
*/
private Runnable respRate_Average_Tick = new Runnable() {
public void run() {
if (mRespRateIndex == 0)
return;
final int rrAvg;
synchronized(mRespRateAverageLock) {
rrAvg = mRespRateTotal / mRespRateIndex;
mRespRateTotal = 0;
mRespRateIndex = 0;
}
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.AVERAGE_RESP_RATE, rrAvg);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
};
/**
* Goes through all all parameters in "keyItems", it saves the visible ones to the long array toggledIds[]
* Then calls setVisibleIds to save this long list to a string list in SharedPref at
*
* "results_visible_ids_measure2"
*/
private void saveVisibleKeyIds() {
ArrayList<Long> toggledIds = new ArrayList<Long>();
for(int i = 0; i < mBioParameters.size(); ++i) {
GraphBioParameter item = mBioParameters.get(i);
if(item.visible) {
toggledIds.add(item.id);
}
}
setVisibleIds(KEY_NAME, toggledIds);
}
/**
* Saves long array of ids to a string array at
* "results_visible_ids_measure3"
*
* @param keySuffix Id of the array
* @return a long array containing ids of parameters that are visible
*/
private void setVisibleIds(String keyName, ArrayList<Long> ids) {
SharedPref.setValues(
sharedPref,
keyName,
",",
ArraysExtra.toStringArray(ids.toArray(new Long[ids.size()]))
);
}
/**
*
* @param keySuffix id of the array
* @param ids long array of ids to save to Shared Params
*/
private ArrayList<Long> getVisibleIds(String keyName) {
String[] idsStrArr = SharedPref.getValues(
sharedPref,
keyName,
",",
new String[0]
);
return new ArrayList<Long>(
Arrays.asList(
ArraysExtra.toLongArray(idsStrArr)
)
);
}
/**
* Returns a unique key color based on the current index and total count of parameters
* @param currentIndex Index of current parameter
* @param totalCount Total number of parameters
* @return Unique color based in inputs
*/
protected int getKeyColor(int currentIndex, int totalCount) {
float hue = currentIndex / (1.00f * totalCount) * 360.00f;
return Color.HSVToColor(
255,
new float[]{
hue,
1.0f,
1.0f
}
);
}
public void onButtonClick(View v)
{
final int id = v.getId();
switch (id) {
// case R.id.buttonBack:
// finish();
//
// break;
//
case R.id.buttonAddMeasure:
boolean toggleArray[] = new boolean[mBioParameters.size()];
for(int j = 0; j < mBioParameters.size(); ++j) {
GraphBioParameter item = mBioParameters.get(j);
if(item.visible)
toggleArray[j] = true;
else
toggleArray[j] = false;
}
String[] measureNames = new String[mBioParameters.size()];
int i = 0;
for (GraphBioParameter item: mBioParameters) {
measureNames[i++] = item.title1;
}
// Present dialog to allow user to choose which parameters to view in this activity
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle(R.string.alert_dialog_measure_selector);
// alert.setMultiChoiceItems(R.array.measure_select_dialog_items,
alert.setMultiChoiceItems(measureNames,
toggleArray,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int whichButton,boolean isChecked) {
GraphBioParameter item = mBioParameters.get(whichButton);
item.visible = item.visible ? false: true;
saveVisibleKeyIds();
generateChart();
}
});
alert.setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
generateChart();
}
});
alert.show();
break;
case R.id.buttonPause:
if (mPaused == true) {
mPaused = false;
mPauseButton.getBackground().setColorFilter(Color.LTGRAY, PorterDuff.Mode.MULTIPLY);
mPauseButton.setText(R.string.button_running);
if (mLoggingEnabled) {
try {
mDataOutHandler.logNote(getString(R.string.un_paused));
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
} // data header
}
if (mLogCatEnabled) {
Log.d(TAG, "Un-Paused" );
}
}
else {
mPaused = true;
mPauseButton.getBackground().setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
mPauseButton.setText(R.string.button_pause);
if (mLoggingEnabled) {
try {
mDataOutHandler.logNote(getString(R.string.paused));
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
} // data header
}
if (mLogCatEnabled) {
Log.d(TAG, "Paused" );
}
}
break;
} // End switch
}
/**
* Sets up all parameters for display of both the chart on the screen
* AND a color coded display of the parameters and their values
*/
private void generateChart() {
// Set up chart
XYMultipleSeriesDataset deviceDataset = new XYMultipleSeriesDataset();
XYMultipleSeriesRenderer deviceRenderer = new XYMultipleSeriesRenderer();
LinearLayout layout = (LinearLayout) findViewById(R.id.deviceChart);
if (mDeviceChartView != null) {
layout.removeView(mDeviceChartView);
}
if (true) {
mDeviceChartView = ChartFactory.getLineChartView(this, deviceDataset, deviceRenderer);
mDeviceChartView.setBackgroundColor(Color.BLACK);
layout.addView(mDeviceChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
}
deviceRenderer.setShowLabels(false);
deviceRenderer.setMargins(new int[] {0,5,5,0});
deviceRenderer.setShowAxes(true);
deviceRenderer.setShowLegend(false);
deviceRenderer.setZoomEnabled(false, false);
deviceRenderer.setPanEnabled(false, false);
deviceRenderer.setYAxisMin(0);
deviceRenderer.setYAxisMax(100);
SpannableStringBuilder sMeasuresText = new SpannableStringBuilder("Displaying: ");
ArrayList<Long> visibleIds = getVisibleIds(KEY_NAME);
int keyCount = mBioParameters.size();
keyCount = mBioParameters.size();
int lineNum = 0;
for(int i = 0; i < mBioParameters.size(); ++i) {
GraphBioParameter item = mBioParameters.get(i);
item.visible = visibleIds.contains(item.id);
if(!item.visible) {
continue;
}
deviceDataset.addSeries(item.series);
item.color = getKeyColor(i, keyCount);
// Add name of the measure to the displayed text field
ForegroundColorSpan fcs = new ForegroundColorSpan(item.color);
int start = sMeasuresText.length();
sMeasuresText.append(mBioParameters.get(i).title1 + ", ");
int end = sMeasuresText.length();
sMeasuresText.setSpan(fcs, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
if (sMeasuresText.length() > 40 && lineNum == 0) {
lineNum++;
}
XYSeriesRenderer seriesRenderer = new XYSeriesRenderer();
seriesRenderer.setColor(item.color);
seriesRenderer.setPointStyle(PointStyle.CIRCLE);
deviceRenderer.addSeriesRenderer(seriesRenderer);
}
mMeasuresDisplayText.setText(sMeasuresText) ;
}
/**
* Receives a json string containing data about all of the paired sensors
* the adds a new BioSensor for each one to the mBioSensors collection
*
* @param jsonString String containing info on all paired devices
*/
private void populateBioSensors(String jsonString) {
Log.d(TAG, this.getClass().getSimpleName() + " populateBioSensors");
// Now clear it out and populate it. The only difference is that
// if a sensor previously existed, then
mBioSensors.clear();
try {
JSONArray jsonArray = new JSONArray(jsonString);
for (int i = 0; i < jsonArray.length(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
Boolean enabled = jsonObject.getBoolean("enabled");
String name = jsonObject.getString("name");
String address = jsonObject.getString("address");
int connectionStatus = jsonObject.getInt("connectionStatus");
if (name.equalsIgnoreCase("system")) {
mBluetoothEnabled = enabled;
}
else {
Log.d(TAG, "Adding sensor " + name + ", " + address + (enabled ? ", enabled":", disabled") + " : " + Util.connectionStatusToString(connectionStatus));
BioSensor bioSensor = new BioSensor(name, address, enabled);
bioSensor.mConnectionStatus = connectionStatus;
mBioSensors.add(bioSensor);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
}
}
/**
* Validates sensors, makes sure that bluetooth is on and each sensor has a parameter associated with it
*/
void validateBioSensors() {
// First make sure that bluetooth is enabled
if (!mBluetoothEnabled) {
AlertDialog.Builder alert1 = new AlertDialog.Builder(this);
alert1.setMessage("Bluetooth is not enabled on your device. Press OK to go to the wireless system" +
"settings to turn it on");
alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
mInstance.startActivityForResult(new Intent(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS), BLUETOOTH_SETTINGS_ID);
}
});
alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
alert1.show();
}
String badSensorName = null;
// Now make sure that every device has a parameter associated with it
for (BioSensor sensor: mBioSensors) {
if (sensor.mEnabled) {
String param = SharedPref.getParamForDevice(mInstance, sensor.mBTAddress);
//Log.d(TAG, "sensor: " + sensor.mBTName + ", parameter: " + param);
if (param == null) {
badSensorName = sensor.mBTName;
break;
}
}
} // end for (BioSensor sensor: mBioSensors)
if (badSensorName != null) {
AlertDialog.Builder alert1 = new AlertDialog.Builder(this);
alert1.setMessage("Sensor " + badSensorName + " is enabled but " +
" does not have a parameter associated with it." +
"Press Ok to associate one");
alert1.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Intent intent2 = new Intent(mInstance, DeviceManagerActivity.class);
mInstance.startActivity(intent2);
}
});
alert1.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
alert1.show();
}
}
/**
* Wrapper for BioParameter that has a graph element
*
* @author scott.coleman
*
*/
static class GraphBioParameter extends BioParameter{
public XYSeries series;
public Boolean isShimmer;
Node shimmerNode;
byte shimmerSensorConstant;
public GraphBioParameter(long id, String title1, String title2, Boolean enabled) {
super(id, title1, title2, enabled);
isShimmer = false;
shimmerNode = null;
shimmerSensorConstant = 0;
series = new XYSeries(title1);
}
}
// ----------------------------------------------------------
// ANT specific stuff
//-----------------------------------------------------------
@Override
public void errorCallback() {
Log.e(TAG, "ANT error");
}
@Override
public void notifyAntStateChanged() {
// TODO Auto-generated method stub
}
@Override
public void notifyChannelStateChanged(byte channel) {
// TODO Auto-generated method stub
}
@Override
public void notifyChannelDataChanged(byte channel) {
// Log.i(TAG, "notifyChannelDataChanged");
HeartBeatData thisData = new HeartBeatData();
thisData.setFunctionCode(SPINEFunctionConstants.HEARTBEAT);
thisData.setBPM(mAntManager.getBPM());
this.received(thisData);
}
private final ServiceConnection mConnection = new ServiceConnection()
{
@Override
public void onServiceDisconnected(ComponentName name)
{
//This is very unlikely to happen with a local service (ie. one in the same process)
mAntManager.setCallbacks(null);
mAntManager = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service)
{
mAntManager = ((ANTPlusService.LocalBinder)service).getManager();
mAntManager.setCallbacks(Graphs1Activity.this);
loadAntState();
notifyAntStateChanged();
// Start ANT automatically
mAntManager.doEnable();
Log.i(TAG, "Starting heart rate data");
mAntManager.openChannel(AntPlusManager.HRM_CHANNEL, true);
mAntManager.requestReset();
}
};
/**
* Store application persistent data.
*/
private void saveAntState()
{
// Save current Channel Id in preferences
// We need an Editor object to make changes
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt("DeviceNumberHRM", mAntManager.getDeviceNumberHRM());
editor.putInt("DeviceNumberSDM", mAntManager.getDeviceNumberSDM());
editor.putInt("DeviceNumberWGT", mAntManager.getDeviceNumberWGT());
editor.putInt("ProximityThreshold", mAntManager.getProximityThreshold());
editor.putInt("BufferThreshold", mAntManager.getBufferThreshold());
editor.commit();
}
/**
* Retrieve application persistent data.
*/
private void loadAntState()
{
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
mAntManager.setDeviceNumberHRM((short) settings.getInt("DeviceNumberHRM", ANT_WILDCARD));
mAntManager.setDeviceNumberSDM((short) settings.getInt("DeviceNumberSDM", ANT_WILDCARD));
mAntManager.setDeviceNumberWGT((short) settings.getInt("DeviceNumberWGT", ANT_WILDCARD));
mAntManager.setProximityThreshold((byte) settings.getInt("ProximityThreshold", ANT_DEFAULT_BIN));
mAntManager.setBufferThreshold((short) settings.getInt("BufferThreshold", ANT_DEFAULT_BUFFER_THRESHOLD));
}
private long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
private double map(double x, double in_min, double in_max, double out_min, double out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
}